package plp.orientadaObjetos2.ambiente;

import plp.expressions2.memory.VariavelJaDeclaradaException;
import plp.expressions2.memory.VariavelNaoDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ClasseJaDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ClasseNaoDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ObjetoJaDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ObjetoNaoDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ProcedimentoJaDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ProcedimentoNaoDeclaradoException;
import plp.orientadaObjetos1.excecao.execucao.EntradaInvalidaException;
import plp.orientadaObjetos1.expressao.ListaExpressao;
import plp.orientadaObjetos1.expressao.leftExpression.Id;
import plp.orientadaObjetos1.expressao.valor.Valor;
import plp.orientadaObjetos1.expressao.valor.ValorInteiro;
import plp.orientadaObjetos1.expressao.valor.ValorNull;
import plp.orientadaObjetos1.expressao.valor.ValorString;
import plp.orientadaObjetos1.memoria.colecao.ListaValor;
import plp.orientadaObjetos2.comando.ChamadaMetodoOO2;
import plp.orientadaObjetos2.declaracao.funcao.DecFuncao;
import plp.orientadaObjetos2.expressao.ChamadaFuncao;
import plp.orientadaObjetos2.memoria.AmbienteExecucaoOO2;
import plp.orientadaObjetos2.memoria.DefClasseOO2;
import plp.orientadaObjetos2.memoria.FuncaoNaoDeclaradaException;

public class Metodo {

	private Id nomeProcedimentoOuFuncao;

	private Classe classe;


	public Metodo(Id nomeProcedimentoOuFuncao, Classe classe) {
		super();
		this.nomeProcedimentoOuFuncao = nomeProcedimentoOuFuncao;
		this.classe = classe;
	}



	public Valor invoca(AmbienteExecucaoOO2 ambiente, ListaValor argumentos){
		Valor retorno = new ValorNull();
			DefClasseOO2 defClasse;
			try {
				defClasse = (DefClasseOO2) ambiente.getDefClasse(this.classe.getIdClasse());
				DecFuncao funcaoConcreta;
				ListaExpressao listaExpressao = ListaValor.getListaExpressao((ListaValor) argumentos.getTail());
				try {
					funcaoConcreta = defClasse.getFuncao(this.nomeProcedimentoOuFuncao);
					try {
						retorno = new ChamadaFuncao(argumentos.getHead(),
													funcaoConcreta.getNome(),
													listaExpressao).
													avaliar(ambiente);
						if(retorno instanceof ValorInteiro){
							return new Classe(Nomes.CLASSE_INTEIRO).instancia(ambiente, new ListaValor(retorno));
						} else if(retorno instanceof ValorString){
							return new Classe(Nomes.CLASSE_STRING).instancia(ambiente, new ListaValor(retorno));
						} else if(retorno instanceof ValorInteiro){
							return new Classe(Nomes.CLASSE_BOOLEANO).instancia(ambiente, new ListaValor(retorno));
						}
					} catch (VariavelNaoDeclaradaException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + funcaoConcreta.getNome(), e);
					} catch (VariavelJaDeclaradaException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + funcaoConcreta.getNome(), e);
					} catch (ObjetoNaoDeclaradoException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + funcaoConcreta.getNome(), e);
					} 
				} catch (FuncaoNaoDeclaradaException e1) {
					try {
						new ChamadaMetodoOO2(argumentos.getHead(), this.nomeProcedimentoOuFuncao, listaExpressao).executar(ambiente);
					} catch (VariavelJaDeclaradaException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					} catch (VariavelNaoDeclaradaException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					} catch (ProcedimentoNaoDeclaradoException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					} catch (ProcedimentoJaDeclaradoException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					} catch (ObjetoJaDeclaradoException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					} catch (ObjetoNaoDeclaradoException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					} catch (ClasseJaDeclaradaException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					} catch (EntradaInvalidaException e) {
						throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
					}
				}
			} catch (ClasseNaoDeclaradaException e1) {
				throw new ExcecaoNativaException("Excecao ao invocar metodo " + nomeProcedimentoOuFuncao.getIdName(), e1);
			}
		return retorno;
	}

	public Valor getNome(AmbienteExecucaoOO2 ambiente, ListaValor argumentos){
		return new ValorString(this.nomeProcedimentoOuFuncao.getIdName());
	}
}
